// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/formats/mp2t/es_adapter_video.h"

#include <stddef.h>

#include "media/base/timestamp_constants.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/mp2t_common.h"

namespace media {
namespace mp2t {

    // Arbitrary decision about the frame duration when there is no previous
    // hint about what could be the frame duration.
    static const int kDefaultFrameDurationMs = 40;

    // To calculate the frame duration, we make an assumption
    // that the timestamp of the next frame in presentation order
    // is no further than 5 frames away in decode order.
    // TODO(damienv): the previous assumption should cover most of the practical
    // cases. However, the right way to calculate the frame duration would be
    // to emulate the H264 dpb bumping process.
    static const size_t kHistorySize = 5;

    EsAdapterVideo::EsAdapterVideo(
        const NewVideoConfigCB& new_video_config_cb,
        const EmitBufferCB& emit_buffer_cb)
        : new_video_config_cb_(new_video_config_cb)
        , emit_buffer_cb_(emit_buffer_cb)
        , has_valid_config_(false)
        , has_valid_frame_(false)
        , last_frame_duration_(
              base::TimeDelta::FromMilliseconds(kDefaultFrameDurationMs))
        , buffer_index_(0)
        , has_valid_initial_timestamp_(false)
        , discarded_frame_count_(0)
    {
    }

    EsAdapterVideo::~EsAdapterVideo()
    {
    }

    void EsAdapterVideo::Flush()
    {
        ProcessPendingBuffers(true);
    }

    void EsAdapterVideo::Reset()
    {
        has_valid_config_ = false;
        has_valid_frame_ = false;

        last_frame_duration_ = base::TimeDelta::FromMilliseconds(kDefaultFrameDurationMs);

        config_list_.clear();
        buffer_index_ = 0;
        buffer_list_.clear();
        emitted_pts_.clear();

        has_valid_initial_timestamp_ = false;
        min_pts_ = base::TimeDelta();
        min_dts_ = DecodeTimestamp();

        discarded_frame_count_ = 0;
    }

    void EsAdapterVideo::OnConfigChanged(
        const VideoDecoderConfig& video_decoder_config)
    {
        config_list_.push_back(
            ConfigEntry(buffer_index_ + buffer_list_.size(), video_decoder_config));
        has_valid_config_ = true;
        ProcessPendingBuffers(false);
    }

    bool EsAdapterVideo::OnNewBuffer(
        const scoped_refptr<StreamParserBuffer>& stream_parser_buffer)
    {
        if (stream_parser_buffer->timestamp() == kNoTimestamp) {
            if (has_valid_frame_) {
                // There is currently no error concealment for a missing timestamp
                // in the middle of the stream.
                DVLOG(1) << "Missing timestamp in the middle of the stream";
                return false;
            }

            if (!has_valid_initial_timestamp_) {
                // MPEG-2 TS requires the first access unit to be given a timestamp.
                // However, some streams do not comply with this requirement.
                // So simply drop the frame if it is a leading frame with no timestamp.
                DVLOG(1)
                    << "Stream not compliant: ignoring leading frame with no timestamp";
                return true;
            }

            // In all the other cases, this frame will be replaced by the following
            // valid key frame, using timestamp interpolation.
            DCHECK(has_valid_initial_timestamp_);
            DCHECK_GE(discarded_frame_count_, 1);
            discarded_frame_count_++;
            return true;
        }

        // At this point, timestamps of the incoming frame are valid.
        if (!has_valid_initial_timestamp_) {
            min_pts_ = stream_parser_buffer->timestamp();
            min_dts_ = stream_parser_buffer->GetDecodeTimestamp();
            has_valid_initial_timestamp_ = true;
        }
        if (stream_parser_buffer->timestamp() < min_pts_)
            min_pts_ = stream_parser_buffer->timestamp();

        // Discard the incoming frame:
        // - if it is not associated with any config,
        // - or if no valid key frame has been found so far.
        if (!has_valid_config_ || (!has_valid_frame_ && !stream_parser_buffer->is_key_frame())) {
            discarded_frame_count_++;
            return true;
        }

        has_valid_frame_ = true;

        if (discarded_frame_count_ > 0)
            ReplaceDiscardedFrames(stream_parser_buffer);

        buffer_list_.push_back(stream_parser_buffer);
        ProcessPendingBuffers(false);
        return true;
    }

    void EsAdapterVideo::ProcessPendingBuffers(bool flush)
    {
        DCHECK(has_valid_config_);

        while (!buffer_list_.empty() && (flush || buffer_list_.size() > kHistorySize)) {
            // Signal a config change, just before emitting the corresponding frame.
            if (!config_list_.empty() && config_list_.front().first == buffer_index_) {
                new_video_config_cb_.Run(config_list_.front().second);
                config_list_.pop_front();
            }

            scoped_refptr<StreamParserBuffer> buffer = buffer_list_.front();
            buffer_list_.pop_front();
            buffer_index_++;

            if (buffer->duration() == kNoTimestamp) {
                base::TimeDelta next_frame_pts = GetNextFramePts(buffer->timestamp());
                if (next_frame_pts == kNoTimestamp) {
                    // This can happen when emitting the very last buffer
                    // or if the stream do not meet the assumption behind |kHistorySize|.
                    DVLOG(LOG_LEVEL_ES) << "Using last frame duration: "
                                        << last_frame_duration_.InMilliseconds();
                    buffer->set_duration(last_frame_duration_);
                } else {
                    base::TimeDelta duration = next_frame_pts - buffer->timestamp();
                    DVLOG(LOG_LEVEL_ES) << "Frame duration: " << duration.InMilliseconds();
                    buffer->set_duration(duration);
                }
            }

            emitted_pts_.push_back(buffer->timestamp());
            if (emitted_pts_.size() > kHistorySize)
                emitted_pts_.pop_front();

            last_frame_duration_ = buffer->duration();
            emit_buffer_cb_.Run(buffer);
        }
    }

    base::TimeDelta EsAdapterVideo::GetNextFramePts(base::TimeDelta current_pts)
    {
        base::TimeDelta next_pts = kNoTimestamp;

        // Consider the timestamps of future frames (in decode order).
        // Note: the next frame is not enough when the GOP includes some B frames.
        for (BufferQueue::const_iterator it = buffer_list_.begin();
             it != buffer_list_.end(); ++it) {
            if ((*it)->timestamp() < current_pts)
                continue;
            if (next_pts == kNoTimestamp || next_pts > (*it)->timestamp())
                next_pts = (*it)->timestamp();
        }

        // Consider the timestamps of previous frames (in decode order).
        // In a simple GOP structure with B frames, the frame next to the last B
        // frame (in presentation order) is located before in decode order.
        for (std::list<base::TimeDelta>::const_iterator it = emitted_pts_.begin();
             it != emitted_pts_.end(); ++it) {
            if (*it < current_pts)
                continue;
            if (next_pts == kNoTimestamp || next_pts > *it)
                next_pts = *it;
        }

        return next_pts;
    }

    void EsAdapterVideo::ReplaceDiscardedFrames(
        const scoped_refptr<StreamParserBuffer>& stream_parser_buffer)
    {
        DCHECK_GT(discarded_frame_count_, 0);
        DCHECK(stream_parser_buffer->is_key_frame());

        // PTS/DTS are interpolated between the min PTS/DTS of discarded frames
        // and the PTS/DTS of the first valid buffer.
        // Note: |pts_delta| and |dts_delta| are calculated using integer division.
        // Interpolation thus accumulutes small errors. However, since timestamps
        // are given in microseconds, only a high number of discarded frames
        // (in the order of 10000s) could have an impact and create a gap (from MSE
        // point of view) between the last interpolated frame and
        // |stream_parser_buffer|.
        base::TimeDelta pts = min_pts_;
        base::TimeDelta pts_delta = (stream_parser_buffer->timestamp() - pts) / discarded_frame_count_;
        DecodeTimestamp dts = min_dts_;
        base::TimeDelta dts_delta = (stream_parser_buffer->GetDecodeTimestamp() - dts) / discarded_frame_count_;

        for (int i = 0; i < discarded_frame_count_; i++) {
            scoped_refptr<StreamParserBuffer> frame = StreamParserBuffer::CopyFrom(
                stream_parser_buffer->data(),
                stream_parser_buffer->data_size(),
                stream_parser_buffer->is_key_frame(),
                stream_parser_buffer->type(),
                stream_parser_buffer->track_id());
            frame->SetDecodeTimestamp(dts);
            frame->set_timestamp(pts);
            frame->set_duration(pts_delta);
            buffer_list_.push_back(frame);
            pts += pts_delta;
            dts += dts_delta;
        }
        discarded_frame_count_ = 0;
    }

} // namespace mp2t
} // namespace media
